using System;
using System.Collections.Generic;
using System.Linq;

using JetBrains.Annotations;

using Rsdn.SmartApp.FW40;

namespace Rsdn.SmartApp.CommandLine
{
	/// <summary>
	/// Implementation of <see cref="ICommandLineService"/>
	/// </summary>
	public class CommandLineService : ICommandLineService
	{
		private readonly IServiceProvider _serviceProvider;
		private readonly Func<CommandLineRulesProviderInfo[]> _providerInfosGetter;
		private readonly CommandQuantifier _commandQuantifier;
		private readonly object _initLockFlag = new object();
		private bool _inited;
		private CmdLineNode _parsedCmdLine;

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		public CommandLineService(
			[NotNull] IServiceProvider serviceProvider,
			[NotNull] Func<CommandLineRulesProviderInfo[]> providerInfosGetter,
			CommandQuantifier commandQuantifier)
		{
			if (serviceProvider == null)
				throw new ArgumentNullException("serviceProvider");
			if (providerInfosGetter == null)
				throw new ArgumentNullException("providerInfosGetter");

			_serviceProvider = serviceProvider;
			_providerInfosGetter = providerInfosGetter;
			_commandQuantifier = commandQuantifier;
		}

		private void EnsureInit()
		{
			if (!_inited)
				lock (_initLockFlag)
					if (!_inited)
					{
						Init();
						_inited = true;
					}
		}

		private void Init()
		{
			var ast = CommandLineHelper.ParseCommandLine(GetCommandLine());

			var infos = _providerInfosGetter();
			var cmdRules = new List<CommandRule>();
			var optRules = new List<OptionRule>();
			foreach (var info in infos)
			{
				var provider = (ICommandLineRulesProvider)info.ProviderType.CreateInstance(_serviceProvider);
				cmdRules.AddRange(provider.GetCommands());
				optRules.AddRange(provider.GetOptions());
			}
			var rules = new CmdLineRules(_commandQuantifier, cmdRules.ToArray(), optRules.ToArray());
			CommandLineHelper.Check(ast, rules);
			_parsedCmdLine = ast;
		}

        /// <summary>
        /// Gets the command line.
        /// </summary>
        /// <returns></returns>
		protected virtual string GetCommandLine()
		{
			return Environment.CommandLine;
		}

		#region Implementation of ICommandLineService
        /// <summary>
        /// Returns file name, that used to execute program.
        /// </summary>
        /// <returns></returns>
		public string GetProgramName()
		{
			EnsureInit();
			return _parsedCmdLine.ProgramName.Text;
		}

        /// <summary>
        /// Return all specified commands.
        /// </summary>
        /// <returns></returns>
		public string[] GetCommands()
		{
			EnsureInit();
			return _parsedCmdLine.Commands.Select(cmd => cmd.Text).ToArray();
		}

        /// <summary>
        /// Returns true, if specified command defined.
        /// </summary>
        /// <param name="commandName"></param>
        /// <returns></returns>
		public bool IsCommandDefined(string commandName)
		{
			EnsureInit();
			return _parsedCmdLine.Commands.Any(cmd => cmd.Text == commandName);
		}

        /// <summary>
        /// Return all specified options.
        /// </summary>
        /// <returns></returns>
		public string[] GetOptions()
		{
			EnsureInit();
			return _parsedCmdLine.Options.Select(opt => opt.Text).ToArray();
		}

        /// <summary>
        /// Returns true, if specified option defined.
        /// </summary>
        /// <param name="optionName"></param>
        /// <returns></returns>
		public bool IsOptionDefined(string optionName)
		{
			EnsureInit();
			return _parsedCmdLine.Options.Any(opt => opt.Text == optionName);
		}

        /// <summary>
        /// Gets the bool option value.
        /// </summary>
        /// <param name="optionName">Name of the option.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"><c>ArgumentException</c>.</exception>
		public bool GetBoolOptionValue(string optionName)
		{
			EnsureInit();
			var option = _parsedCmdLine.Options.FirstOrDefault(opt => opt.Text == optionName);
            if (option == null)
                throw new ArgumentException(
                    FW40Resources
                        .CommandLineService_OptionNotSpecified_
						.FormatStr(optionName));
            if (option.Type != OptionType.Bool)
                throw new ArgumentException(
                    FW40Resources
                        .CommandLineService_OptionIsNotBool
						.FormatStr(optionName));
			return option.BoolValue;
		}

        /// <summary>
        /// Gets the value option value.
        /// </summary>
        /// <param name="optionName">Name of the option.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"><c>ArgumentException</c>.</exception>
		public string GetValueOptionValue(string optionName)
		{
			EnsureInit();
			var option = _parsedCmdLine.Options.FirstOrDefault(opt => opt.Text == optionName);
            if (option == null)
                throw new ArgumentException(
                    FW40Resources
                        .CommandLineService_OptionNotSpecified_
						.FormatStr(optionName));
			if (option.Type != OptionType.Value)
				throw new ArgumentException(
					FW40Resources
						.CommandLineService_OptionIsNotValue
						.FormatStr(optionName));
			return option.Value.Text;
		}
		#endregion
	}
}